<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="keyword" content="">
    <link rel="shortcut icon" href="/node-mongodb-native/img/favicon.png">

    <title>Account Transactions</title>

    <link href="/node-mongodb-native/css/bootstrap-theme.css" rel="stylesheet">
    <link href="/node-mongodb-native/assets/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
    <link href="/node-mongodb-native/css/style.css" rel="stylesheet">
    <link href="/node-mongodb-native/css/style-responsive.css" rel="stylesheet" />
    <link href="/node-mongodb-native/css/monokai_sublime.css" rel="stylesheet" />

  </head>

  <body>

  <section id="container" class="">


      <header class="header black-bg">
            <div class="toggle-nav">
                <i class="fa fa-bars"></i>
                <div class="icon-reorder tooltips" data-original-title="Toggle Navigation" data-placement="bottom"></div>
            </div>


            <a href="/node-mongodb-native/" class="logo"><img src="/node-mongodb-native/img/logo-mongodb-header.png" style="height:40px;"></a>

            <div class="nav title-row" id="top_menu">
                <h1 class="nav top-menu"> Account Transactions </h1>
            </div>
      </header>



<aside>
    <div id="sidebar"  class="nav-collapse ">

        <ul class="sidebar-menu">




            <li class="sub-menu active">
            <a href="javascript:;" class="">
                <i class='fa fa-book'></i>

                <span>schema design</span>
                <span class="menu-arrow fa fa-angle-down"></span>
            </a>
                <ul class="sub open">

                    <li><a href="/node-mongodb-native/schema/chapter1/"> Introduction </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter2/"> Schema Basics </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter3/"> MongoDB Storage </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter4/"> Indexes </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter5/"> Metadata </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter6/"> Time Series </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter7/"> Queues </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter8/"> Nested Categories </a> </li>

                    <li class="active"><a href="/node-mongodb-native/schema/chapter9/"> Account Transactions </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter10/"> Shopping Cart </a> </li>

                    <li><a href="/node-mongodb-native/schema/chapter11/"> Sharding </a> </li>

                </ul>

              </li>


                <li>
                <a class="" href="https://mongodb.github.io/node-mongodb-native/2.2">
                    <i class='fa fa-file-text'></i>

                    <span>2.0 driver docs</span>
                </a>

              </li>


                <li>
                <a class="" href="https://mongodb.github.io/node-mongodb-native/2.2">
                    <i class='fa fa-file-text'></i>

                    <span>1.4 driver docs</span>
                </a>

              </li>


                <li>
                <a class="" href="https://mongodb.github.io/node-mongodb-native/2.2">
                    <i class='fa fa-file-text'></i>

                    <span>Core driver docs</span>
                </a>

              </li>

            <li> <a href="https://groups.google.com/forum/#!forum/learn-mongo-db-the-hardway" target="blank"><i class='fa fa-life-ring'></i>Issues & Help</a> </li>


        </ul>

    </div>
</aside>




      <section id="main-content">
          <section class="wrapper">













              <div class="row">
                  <div class="col-md-1">


                      <a class="navigation prev" href="/schema/chapter8">
                      <i class="fa fa-angle-left"></i>
                      </a>


                  </div>
                <div class="col-md-10">
                    <section class="panel">



                    <div class="panel-body">



<h1 id="account-transactions:38a9a76b33f1cbc2606833c72cbf6c68">Account Transactions</h1>

<p><img src="/images/originals/bank.png" alt="Metadata, courtesy of http://www.flickr.com/photos/68751915@N05/6629034769" />
</p>

<p>MongoDB does not as of 2.6 support any notion of a transaction across a document or multiple documents. It does however guarantee atomic operations on single documents. This allows us to implement a Two-Phase commit strategy using double bookkeeping. However it&rsquo;s important to note that due to only single document operations being atomic, MongoDB can only offer transaction-like semantics. It&rsquo;s still possible for applications to return intermediate during the two-phase commit or rollback.</p>

<p>In this chapter we will use two collections to simulate a bank account system. The first collection <strong>accounts</strong> contains all the customer accounts and the second one <strong>transactions</strong> is our transaction book-keeping collection. The goal is to transfer <em>100</em> from Joe to Peter using a two-phase commit.</p>

<h2 id="two-phase-commit:38a9a76b33f1cbc2606833c72cbf6c68">Two-phase Commit</h2>

<p>First create two accounts</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).accounts;
col.insert({name: &quot;Joe&quot;, balance: 1000, pendingTransactions:[]});
col.insert({name: &quot;Peter&quot;, balance: 1000, pendingTransactions:[]});
</code></pre>

<p>Let&rsquo;s set up the initial transaction</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.insert({source: &quot;Joe&quot;, destination: &quot;Peter&quot;, amount: 100, state: &quot;intital&quot;});
</code></pre>

<p>Let&rsquo;s first update the transaction to pending</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
var transaction = col.findOne({state: &quot;initial&quot;});
col.update({_id: transaction._id}, {$set: {state: &quot;pending&quot;}});
</code></pre>

<p>Next apply the transaction to both accounts</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).accounts;
col.update({
    name: transaction.source, pendingTransactions: {$ne: transaction._id}
  }, {
    $inc: {balance: -transaction.value}, $push: {pendingTransactions: transaction._id}
  });
col.update({
    name: transaction.source, pendingTransactions: {$ne: transaction._id}
  }, {
    $inc: {balance: transaction.value} , $push: {pendingTransactions: transaction._id}
  });
</code></pre>

<p>Set the transaction to committed state</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.update({_id: transaction._id}, {$set: {state: &quot;commited&quot;}});
</code></pre>

<p>Remove the pending transactions from the accounts</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).accounts;
col.update({name: transaction.source}, {$pull: {pendingTransactions: transaction._id}});
col.update({name: transaction.destination}, {$pull: {pendingTransactions: transaction._id}});
</code></pre>

<p>Finally set the transaction state to done</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.update({_id: transaction._id}, {$set: {state: &quot;done&quot;}});
</code></pre>

<h2 id="rollback:38a9a76b33f1cbc2606833c72cbf6c68">Rollback</h2>

<p>There are two types of errors during a two-phase commit that might force us to rollback the transaction.</p>

<ol>
<li>There is an error before applying the transaction to the accounts. To recover from this all transactions marked <strong>pending</strong> need to be retrieved and the application must retry applying the transaction to the accounts.</li>
<li>There is an error after applying the transaction to the accounts but before marking the transaction as <strong>done</strong>. To recover from this all transactions marked <strong>committed</strong> need to be retrieved and start from removing the pending transactions.</li>
</ol>

<p>Correcting for the two error cases will allow an application to resume a transaction a get the accounts to a consistent state.</p>

<blockquote>
<p>Recovery Process</p>

<p>It&rsquo;s helpful to have a process that will look for for any transactions left in <strong>pending</strong> or <strong>commited</strong> state. To determine the time a transaction has been sitting in an interrupted state it might be worth adding a <strong>create_at</strong> timestamp.</p>
</blockquote>

<p>But for some cases you might need to undo (rollback) a transaction due to the application canceling the transaction or because it cannot be recovered (for example if the one of the accounts does not exist during the transaction).</p>

<p>There are two points in the two-phase commit we can rollback.</p>

<ol>
<li>If you have applied the transaction to the accounts you should not rollback. Instead create a new transaction and switch original source and destination fields.</li>
<li>If you have created the transaction but have not yet applied it you can use the following steps.</li>
</ol>

<p>First set the transaction state to canceling</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.update({_id: transaction._id}, {$set: {state: &quot;canceling&quot;}});
</code></pre>

<p>Next let&rsquo;s undo the transaction. Notice that a non-applied transaction means the transaction <strong>_id</strong> has not yet been removed from the <strong>pendingTransactions</strong> array.</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).accounts;
col.update({
    name: transaction.source, pendingTransactions: transaction._id
  }, {
    $inc: {balance: transaction.value}, $pull: {pendingTransactions: transaction._id}
  });
col.update({
    name: transaction.destination, pendingTransactions: transaction._id
  }, {
    $inc: {balance: -transaction.value} , $pull: {pendingTransactions: transaction._id}
  });
</code></pre>

<p>Finally set the transaction state to canceled</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.update({_id: transaction._id}, {$set: {state: &quot;canceled&quot;}});
</code></pre>

<h2 id="concurrent-transaction-application:38a9a76b33f1cbc2606833c72cbf6c68">Concurrent Transaction Application</h2>

<p>Let&rsquo;s imagine to applications <strong>A1</strong> and <strong>A2</strong> that both start processing the single transaction <strong>T1</strong>. Given that the transaction is still in initial then.</p>

<ol>
<li><strong>A1</strong> can apply <strong>T1</strong> before <strong>A2</strong> starts</li>
<li><strong>A2</strong> will then apply <strong>T1</strong> again because it does not appear as pending in the <strong>accounts</strong> documents.</li>
</ol>

<p>You can avoid this by making is explicit in the transaction which application is handling it.</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.findAndModify({
    query: {state: &quot;initial&quot;, application: {$exists: 0}}
  , update: {$set: {state: &quot;pending&quot;, application: &quot;A1&quot;}}
  , new: true});
</code></pre>

<p><strong>findAndModify</strong> will retrieve and update the document atomically. This guarantees that only a single application can tag a transaction as being processed by it. In this case a transaction in the <strong>initial</strong> state is marked as being processed by <strong>A1</strong> if the <strong>application</strong> field does not exist.</p>

<p>If the transaction fails or needs to be rolled back, you can retrieve the <strong>pending</strong> transactions for a specific application <strong>A1</strong>.</p>

<pre><code class="language-js">var col = db.getSisterDB(&quot;bank&quot;).transactions;
col.transactions.find({application: &quot;A1&quot;, state: &quot;pending&quot;});
</code></pre>

<blockquote>
<p>Notes</p>

<p>Real world applications will likely be more complex requiring updating more than just the balance of the accounts. The application might need to update pending credits, pending debits as well as to assure that the account has sufficient balance to cover the transaction.</p>

<p>If these fields are part of the account document they can still occur within a single <strong>update</strong> ensuing an <strong>atomic</strong> update of all the fields.</p>
</blockquote>



                <div class="col-md-1">


                    <a class="navigation next" href="/schema/chapter10">
                        <i class="fa fa-angle-right"> </i>
                    </a>


                </div>
              </div>

          </section>
      </section>

  </section>


    <script src="/node-mongodb-native/js/jquery-2.1.1.min.js"></script>
    <script src="/node-mongodb-native/js/jquery.scrollTo.min.js"></script>
    <script src="/node-mongodb-native/js/bootstrap.min.js"></script>

    <script src="/node-mongodb-native/js/highlight.pack.js"></script>
    <script>hljs.initHighlightingOnLoad();</script>
    <script src="/node-mongodb-native/js/scripts.js"></script>
    <script async defer id="github-bjs" src="/node-mongodb-native/js/buttons.js"></script>
    <script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-41953057-1', 'auto');
  ga('send', 'pageview');

</script>
  </body>
</html>
